﻿using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace tactics
{
    using CoordPair = Utility.Pair<int, int>;
    
    public class Cursor
    {        
        GraphicsDevice graphics;
        SpriteBatch spriteBatch;
        Texture2D texture;
        Vector3 point1;
        Vector3 point2;
        Vector3 point3;
        Vector3 point4;

        TacticsCamera camera;

        public Cursor(GraphicsDevice graphicsIn, SpriteBatch spriteBatchIn, Texture2D textureIn, TacticsCamera cameraIn, TerrainMap terrainMap)
        {
            graphics = graphicsIn;
            spriteBatch = spriteBatchIn;
            texture = textureIn;
            camera = cameraIn;
            point1 = Vector3.Zero;
            point2 = Vector3.Zero;
            point3 = Vector3.Zero;
            point4 = Vector3.Zero;
        }

        public CoordPair GetHighlightCoords()
        {
            return new CoordPair((int)point1.X, (int)point1.Z);
        }

        public void Draw()
        {
            MouseState mouseState = Mouse.GetState();
            if (mouseState.RightButton != ButtonState.Pressed)
            {
                Vector2 mousePos = new Vector2(mouseState.X, mouseState.Y);
                spriteBatch.Begin();
                spriteBatch.Draw(texture, mousePos, Color.White);
                spriteBatch.End();
            }
        }

        public void UpdateMouseOver(TerrainMap terrainMap)
        {
            Pick(terrainMap);
        }

        public void DrawHighlight(bool unitInFocus)
        {
            Highlight(unitInFocus);
        }

        private Ray CalculateCursorRay(Matrix projectionMatrix, Matrix viewMatrix)
        {
            MouseState mouseState = Mouse.GetState();
            // create 2 positions in screenspace using the cursor position. 0 is as
            // close as possible to the camera, 1 is as far away as possible.
            Vector3 nearSource = new Vector3(mouseState.X, mouseState.Y, 0f);
            Vector3 farSource = new Vector3(mouseState.X, mouseState.Y, 1f);

            // use Viewport.Unproject to tell what those two screen space positions
            // would be in world space. we'll need the projection matrix and view
            // matrix, which we have saved as member variables. We also need a world
            // matrix, which can just be identity.
            Vector3 nearPoint = graphics.Viewport.Unproject(nearSource,
                projectionMatrix, viewMatrix, Matrix.Identity);

            Vector3 farPoint = graphics.Viewport.Unproject(farSource,
                projectionMatrix, viewMatrix, Matrix.Identity);

            // find the direction vector that goes from the nearPoint to the farPoint
            // and normalize it.
            Vector3 direction = farPoint - nearPoint;
            direction.Normalize();

            // and then create a new ray using nearPoint as the source.
            return new Ray(nearPoint, direction);
        }

        private void Pick(TerrainMap terrainMap)
        {
            Ray ray = CalculateCursorRay(camera.CameraProjection, camera.CameraView);
            
            int width = terrainMap.Width;
            int height = terrainMap.Height;

            point1 = new Vector3(-1, -1, -1);
            point2 = Vector3.Zero;
            point3 = Vector3.Zero;
            point4 = Vector3.Zero;

            int hitx = -1;
            int hity = -1;

            for (int y = 0; y < height - 1; ++y)
            {
                for (int x = 0; x < width - 1; ++x)
                {
                    var collision = Nuclex.Ray3HeightFieldCollider.findQuadContacts(ray.Position, ray.Direction, x, y, terrainMap.NuclexHeightField);
                    
                    if (collision.HasContact)
                    {
                        hitx = x;
                        hity = y;
                    }
                }
            }

            if (hitx != -1 && hity != -1)
            {
                point1 = new Vector3(hitx,      terrainMap.GetHeight(hitx,      hity),      hity);
                point2 = new Vector3(hitx,      terrainMap.GetHeight(hitx,      hity + 1),  hity + 1);
                point3 = new Vector3(hitx + 1, terrainMap.GetHeight(hitx + 1, hity), hity);
                point4 = new Vector3(hitx + 1, terrainMap.GetHeight(hitx + 1, hity + 1), hity + 1);
            }
        }

        private void Highlight(bool unitInFocus)
        {
            if (point1 != Vector3.Zero)
            {

                BasicEffect effect = new BasicEffect(graphics, null);

                //Color blue = new Color(new Vector4(0f, 0f, 255f, 0.5f));
                VertexPositionTexture[] pickedTriangle =
                {
                    new VertexPositionTexture(new Vector3(point1.X, point1.Y + 0.02f, point1.Z), new Vector2(1, 1)),
                    new VertexPositionTexture(new Vector3(point2.X, point2.Y + 0.02f, point2.Z), new Vector2(1, 0)),
                    new VertexPositionTexture(new Vector3(point3.X, point3.Y + 0.02f, point3.Z), new Vector2(0, 1)),
                    new VertexPositionTexture(new Vector3(point4.X, point4.Y + 0.02f, point4.Z), new Vector2(0, 0))
                };

                VertexDeclaration vertDecl = new VertexDeclaration(graphics, VertexPositionTexture.VertexElements);
                
                graphics.RenderState.AlphaBlendOperation = BlendFunction.Add;
                graphics.RenderState.SeparateAlphaBlendEnabled = false;
                graphics.RenderState.AlphaFunction = CompareFunction.Greater;
                graphics.RenderState.ReferenceAlpha = 0;
                graphics.RenderState.AlphaBlendEnable = true;
                graphics.RenderState.AlphaTestEnable = true;
                graphics.RenderState.SourceBlend = Blend.SourceAlpha;
                graphics.RenderState.DestinationBlend = Blend.SourceAlpha;

                graphics.SamplerStates[0].AddressU = TextureAddressMode.Clamp;
                graphics.SamplerStates[0].AddressV = TextureAddressMode.Clamp;
                graphics.RenderState.DepthBufferEnable = false;
                //graphics.RenderState.DepthBufferEnable = true;
                                
                effect.View = camera.CameraView;
                effect.Projection = camera.CameraProjection;
                effect.World = Matrix.Identity;
                //effect.VertexColorEnabled = true;
                effect.TextureEnabled = true;
                effect.Texture = TacticsEngine.Instance.Content.Load<Texture2D>("target");
                
                if (!unitInFocus)
                    effect.DiffuseColor = new Vector3(0f, 0f, 1f);
                else
                    effect.DiffuseColor = new Vector3(.65f, .65f, 0f);

                effect.Begin();

                foreach (EffectPass pass in effect.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    graphics.VertexDeclaration = vertDecl;
                    graphics.DrawUserPrimitives(PrimitiveType.TriangleStrip, pickedTriangle, 0, 2);

                    pass.End();
                }

                effect.End();
            }
        }
    }
}
